#include <Kharlia/Engine/Common.h>
#include <Kharlia/Engine/Actor.h>
#include <Kharlia/Engine/Net.h>
#include <Kharlia/Engine/World.h>
#include <Kharlia/Engine/Level.h>
#include <Kharlia/Engine/Player.h>
#include <Kharlia/Core/ObjectUtil.h>

#ifdef _DEBUG
#define LOG_DEBUG(expression) GetLoggerPtr()->Debug(expression)
#else
#define LOG_DEBUG(expression) 
#endif

#define LOG_INFO(expression) GetLoggerPtr()->Info(expression)

namespace kharlia { namespace engine {

BEGIN_CLASSINFO_NB(AComponent)
END_CLASSINFO()

BEGIN_CLASSINFO(ATransform, AComponent)
    DEF_FIELD(mPosition, FT_VECTOR3F, 0)
END_CLASSINFO()

BEGIN_CLASSINFO(AMovement, AComponent)
    DEF_FIELD(mVelocity, FT_VECTOR3F, 0)
    DEF_FIELD(mAcceleration, FT_VECTOR3F, 0)
    DEF_FIELD(mMaxVelocity, FT_VECTOR3F, 0)
    DEF_FIELD(mMaxAcceleration, FT_VECTOR3F, 0)
END_CLASSINFO()

BEGIN_CLASSINFO(ARender, AComponent)
    DEF_FIELD(mHidden, FT_BOOL, 0)
END_CLASSINFO()

BEGIN_CLASSINFO(ASprite, ARender)
    DEF_FIELD(mImage, FT_RESOURCE, 0)
    DEF_FIELD(mDirection, FT_ENUM, 0)
END_CLASSINFO()

BEGIN_CLASSINFO(ACollider, AComponent)
    DEF_FIELD(mCollideTiles, FT_BOOL, 0)
    DEF_FIELD(mCollideActors, FT_BOOL, 0)
    DEF_FIELD(mBlockActors, FT_BOOL, 0)
    DEF_FIELD(mCollisionLayer, FT_INT, 0)
    DEF_FIELD(mBounds, FT_RECTF, 0)
END_CLASSINFO()

BEGIN_CLASSINFO(ACamera, AComponent)
    DEF_FIELD(mTarget, FT_WEAK_COMPONENT, 0)
    DEF_FIELD(mTargetOffset, FT_VECTOR3F, 0)
END_CLASSINFO()

BEGIN_CLASSINFO(AController, AComponent)
    DEF_FIELD(mPawn, FT_WEAK_COMPONENT, FF_REPNOTIFY)
END_CLASSINFO()

BEGIN_CLASSINFO(APlayerController, AController)
    DEF_FIELD(mCamera, FT_WEAK_COMPONENT, 0)
END_CLASSINFO()

BEGIN_CLASSINFO(APawn, AComponent)
    DEF_FIELD(mController, FT_WEAK_COMPONENT, 0)
END_CLASSINFO()

namespace {

object GetFullClassName(object const& o) {
    object cls = o.attr("__class__");
    object modname = getattr(cls, "__module__", object());
    object name = cls.attr("__name__");
    if (modname.is_none()) {
        return name;
    }
    else {
        return str(".").join(make_tuple(modname, name));
    }
}

const uint8 VI_END = 0;
const uint8 VI_COMP_ADD = 1;
const uint8 VI_COMP_REMOVE = 2;
const uint8 VI_COMP_ADD_CHECKED = 3;
const uint8 VI_COMP_REMOVE_CHECKED = 4;
const uint8 VI_COMP_FULL_DATA = 5;
const uint8 VI_ROLE_CHANGE = 6;
const uint8 VI_REMOTE_ROLE_CHANGE = 7;
const uint8 VI_PYTHON_REP = 8;
const uint8 VI_OWNER_CHANGE = 9;
const uint8 VI_TAG_CHANGE = 10;
const uint8 CI_MSG = 0x3F;

const uint8 MAX_GLOBAL_NAME_SIZE = 128;
const uint8 MAX_TAG_SIZE = 64;

TPtr<APlayerController> GetTopPC(TPtr<CActor> const& actor) {
    TPtr<CActor> topOwner = actor->GetTopOwner();
    return topOwner ? topOwner->GetPlayerController() : actor->GetPlayerController();
}

// Check whether the actor being replicated is owned by the specified connection
bool CheckNetOwner(TPtr<CActor> const& actor, FRMConnection* conn) {
    TPtr<APlayerController> topPC = GetTopPC(actor);
    TPtr<CPlayer> player = topPC ? topPC->GetPlayer() : TPtr<CPlayer>();
    // Replicated player controllers do not have a player
    FRMConnection* pcConn = player ? player->GetRMConnection() : nullptr;
    bool isServer = conn->GetReplicaManager()->GetPeer()->GetIsServer();
    // We're the net owner if we're a server and the player that owns this component
    // is the connection we're replicating to, or if we're a client and the player
    // that owns this component is the local player.
    return isServer ? (pcConn == conn) : (player ? player->GetIsLocal() : false);
}

inline uint16 PackIndex(uint8 a, uint16 b) {
    assert(a < 64 && b < 1024);
    return ((a << 10) & 0xFC00) | (b & 0x3FF);
}

inline void UnpackIndex(uint16 index, uint8& out_a, uint16& out_b) {
    out_a = (index & 0xFC00) >> 10;
    out_b = (index & 0x3FF);
}

inline void WriteIndex(rn::BitStream* bs, uint8 a, uint16 b) {
    bs->Write(PackIndex(a, b));
}

inline bool ReadIndex(rn::BitStream* bs, uint8& out_a, uint16& out_b) {
    uint16 index;
    if (!bs->Read(index))
        return false;
    UnpackIndex(index, out_a, out_b);
    return true;
}

inline void WriteStr(rn::BitStream* bs, object const& s, Py_ssize_t max=PY_SSIZE_T_MAX) {
    assert(PyString_CheckExact(s.ptr()));
    bs->WriteAlignedBytesSafe(PyString_AS_STRING(s.ptr()),
                              PyString_GET_SIZE(s.ptr()),
                              max);
}

inline bool ReadStrToC(rn::BitStream* bs, char** outStr, Py_ssize_t& size, Py_ssize_t max=PY_SSIZE_T_MAX) {
    return bs->ReadAlignedBytesSafeAlloc(outStr, size, max);
}

inline bool ReadStr(rn::BitStream* bs, object& out, Py_ssize_t max=PY_SSIZE_T_MAX) {
    Py_ssize_t size;
    char* bytes = nullptr;
    if (!bs->ReadAlignedBytesSafeAlloc(&bytes, size, max))
        return false;
    assert(bytes);
    try {
        out = str(bytes, size);
    }
    catch (error_already_set&) {
        //::rakFree(bytes);
        rn::_RakFree(bytes);
        throw;
    }
    //rakFree(bytes);
    rn::_RakFree(bytes);
    assert(PyString_CheckExact(out.ptr()));
    return true;
}

void SerializePtr(rn::BitStream* bs, TPtr<CActor> const& actor) {
    if (actor && actor->GetNetwork() &&
            actor->GetRemoteRole() != ENetRole::NONE) {
        rn::NetworkID netid = actor->GetNetwork()->GetNetworkID();
        assert(netid != rn::UNASSIGNED_NETWORK_ID);
        bs->Write(netid);
    }
    else
        bs->Write(rn::UNASSIGNED_NETWORK_ID);
}

void SerializePtr(rn::BitStream* bs, TPtr<AComponent> const& comp) {
    if (!comp) {
        bs->Write(rn::UNASSIGNED_NETWORK_ID);
        return;
    }
    TPtr<CActor> actor = comp->GetActor();
    assert(actor);
    if (!actor->GetNetwork() || actor->GetRemoteRole() == ENetRole::NONE) {
        bs->Write(rn::UNASSIGNED_NETWORK_ID);
        return;
    }
    rn::NetworkID netid = actor->GetNetwork()->GetNetworkID();
    assert(netid != rn::UNASSIGNED_NETWORK_ID);
    int index = actor->GetNetwork()->GetNativeRepIndex(comp);
    if (index == -1) {
        GetLoggerPtr()->Warning(str("Serialize component fail, no native rep index for: ") + str(comp));
        bs->Write(rn::UNASSIGNED_NETWORK_ID);
        return;
    }
    assert(index < CI_MSG);
    bs->Write(netid);
    bs->Write((uint8)index);
}

bool DeserializePtr(rn::BitStream* in, ANetwork* net, TPtr<CActor>& out_actor) {
    rn::NetworkID netid;
    if (!in->Read(netid))
        return false;
    if (netid == rn::UNASSIGNED_NETWORK_ID) {
        out_actor.Reset();
        return true;
    }
    rn::Replica3* rep = static_cast<FReplicaManager*>(net->GetReplicaManager())->
                            GetReplicaByNetworkID(netid);
    if (!rep) {
        out_actor.Reset();
        return true;
    }
    out_actor = static_cast<ANetwork*>(rep)->GetActor();
    return true;
}

bool DeserializePtr(rn::BitStream* in, ANetwork* net, TPtr<AComponent>& out_comp) {
    rn::NetworkID netid;
    uint8 index;

    if (!in->Read(netid))
        return false;
    if (netid == rn::UNASSIGNED_NETWORK_ID) {
        out_comp.Reset();
        return true;
    }
    if (!in->Read(index))
        return false;
    rn::Replica3* rep = static_cast<FReplicaManager*>(net->GetReplicaManager())->
                            GetReplicaByNetworkID(netid);
    if (!rep) {
        out_comp.Reset();
        return true;
    }
    TPtr<AComponent> fcomp = static_cast<ANetwork*>(rep)->GetNativeRepComp(index);
    if (!fcomp) {
        out_comp.Reset();
        return true;
    }
    out_comp = fcomp;
    return true;
}

template <typename T>
T Clamp(T const& value, T const& low, T const& high)  {
    return value < low ? low : (value > high ? high : value); 
}

struct FRecentWeakPtr {
    void* Object;
    void* Unused;
};

static_assert(sizeof(FRecentWeakPtr) == sizeof(TWeakPtr<void>), "");

} // namespace

ANetwork::ANetwork(PyObject* self):
    AComponent(self),
    mHasNativeRep(false),
    mUseNativeRep(false),
    mHasPythonRep(false),
    mUsePythonRep(false),
    mHasSentInitialFields(false),
    mUpdatingComps(false),
    mRecentRole(ENetRole::NONE),
    mRecentRemoteRole(ENetRole::NONE) {

}

ANetwork::~ANetwork() {}

void ANetwork::InitImpl(TPtr<CActor> owner) {
    AComponent::InitImpl(owner);
    InitNativeRep();
    InitPythonRep();
    Reference();
}

void ANetwork::ExitImpl() {
    Dereference();
    if (mHasPythonRep)
        ExitPythonRep();
    if (mHasNativeRep)
        ExitNativeRep();
    AComponent::ExitImpl();
}

bool ANetwork::InitNativeRepComp(TPtr<AComponent> comp) {
    // Retrieve all class field info
    std::vector<FFieldInfo const*> fieldInfo;
    comp->GetFullFieldInfo(fieldInfo);

    // If the component has no networked fields, skip it
    if (fieldInfo.size() < 1) {
        assert(comp->mNetID == 0);
        return false;
    }

    if (mData.size() >= 63)
        throw RuntimeError("maximum number of networked components is 63");

    // Add a sentry field to the list so wecan use a simple array access
    fieldInfo.push_back(GetSentryField());

    // Initialize the fields and determine the storage size
    uint16 size = 0, storageSize = 0;//, idx = 0;
    for (uint16 e = 0; fieldInfo[e]->Type != FT_SENTRY; e++) {
        assert(e <= (1 << 10));
        //if (fieldInfo[e].ArraySize < 1)
        //    idx++;
        //else
        //    idx += fieldInfo[e].ArraySize;
        if (fieldInfo[e]->ClassSize > storageSize)
            storageSize = fieldInfo[e]->ClassSize;
        size++;
    }

    // Initialize fields for this component
    mData.push_back(FRepData());
    FRepData& data = mData[mData.size() - 1];
    data.Component = comp;
    data.Recent.resize(storageSize, 0);
    data.Indices.resize(size * 2 + 1, 0);
    data.Fields = std::move(fieldInfo);
    data.Index = mData.size() - 1;
    if (!mUpdatingComps) {
        assert(GetActor()->GetRole() == ENetRole::AUTHORITY);
        object name = GetFullClassName(comp->GetSelf());
        FRepChange change = {true, mData.size() - 1, name};
        mChanges.push_back(change);
    }
    LOG_DEBUG(str("Inited native replication for: ") + str(comp));
    return true;
}

void ANetwork::CleanupNativeRepComp(TPtr<AComponent> comp) {
    for (uint i = 0; i < mData.size(); i++) {
        if (mData[i].Component.Lock() == comp) {
            mData.erase(mData.begin() + i);
            if (!mUpdatingComps) {
                FRepChange change = {false, i};
                mChanges.push_back(change);
            }
            LOG_DEBUG(str("Cleaned up native replication for: ") + str(comp));
            break;
        }
    }
}

void ANetwork::InitNativeRep() {
    if (mHasNativeRep)
        return;
    assert(mData.size() == 0);
    assert(mChanges.size() == 0);
    std::vector<FComponentEntry> const& comps = GetActor()->GetComponents();
    uint8 cid = 1;
    for (uint i = 0; i < comps.size(); i++) {
        if (comps[i].Component.GetPtr() == this)
            continue;
        InitNativeRepComp(comps[i].Component);
    }
    mRecentRole = ENetRole::NONE;
    mRecentRemoteRole = ENetRole::NONE;
    mHasNativeRep = true;
}

void ANetwork::ExitNativeRep() {
    if (!mHasNativeRep) {
        assert(mData.size() == 0);
        return;
    }
    mData.clear();
    mRecentRole = ENetRole::NONE;
    mRecentRemoteRole = ENetRole::NONE;
    mHasNativeRep = false;
    mChanges.clear();
}

int ANetwork::GetNativeRepIndex(TPtr<AComponent> comp) {
    int index = -1;
    for (uint i = 0; i < mData.size(); i++) {
        if (mData[i].Component.Lock() == comp) {
            index = i;
            break;
        }
    }
    return index;
}

TPtr<AComponent> ANetwork::GetNativeRepComp(int index) {
    if (index < 0 || index >= (int)mData.size())
        return TPtr<AComponent>();
    return mData[index].Component.Lock();
}

void CActor::SetRole(ENetRole::Type role) {
    mRole = role;
}

void CActor::SetRemoteRole(ENetRole::Type role) {
    mRemoteRole = role;
    UpdateNetworkComponent();
}

void CActor::UpdateNetworkComponent() {
    if (!GetNetwork() && mRemoteRole != ENetRole::NONE &&
            GetLevel()->GetWorld()->GetNetPeer()) {
        object netcls = core::FindGlobal("kharlia.engine.actor.ANetwork");
        TPtr<AComponent> comp = extract<TPtr<AComponent>>(netcls());
        AddComponent(comp);
    }
    else if (GetNetwork() && mRemoteRole == ENetRole::NONE) {
        TPtr<ANetwork> comp = GetNetwork();
        RemoveComponent(core::CastPtrStatic<AComponent>(comp));
    }
}

void ANetwork::Reference() {
    auto world = GetLevel()->GetWorld();
    if (!world)
        return;
    FNetPeer* p = world->GetNetPeer();
    if (!p || !p->GetReplicaManager())
        return;
    p->GetReplicaManager()->Reference(this);
    assert(this->replicaManager != nullptr);
}

void ANetwork::Dereference() {
    auto world = GetLevel()->GetWorld();
    if (!world)
        return;
    FNetPeer* p = world->GetNetPeer();
    if (!p || !p->GetReplicaManager())
        return;
    p->GetReplicaManager()->Dereference(this);
    this->replicaManager = nullptr;
}

void ANetwork::InitedComponent(TPtr<AComponent> comp) {
    assert(comp.GetPtr() != this);
    if (GetActor()->GetRole() == ENetRole::AUTHORITY || mUpdatingComps) {
        InitNativeRepComp(comp);
        InitPythonRepComp(comp);
    }
}

void ANetwork::ExitedComponent(TPtr<AComponent> comp) {
    assert(comp.GetPtr() != this);
    CleanupNativeRepComp(comp);
    CleanupPythonRepComp(comp);
}

// MESSAGE INDEXING:
//
// Indices are packed, with 6 bits being used for the component index and 10 bits for
// the variable index. This allows 63 networked components per actor with the last index
// being reserved for control messages.
// 
// Control messages:
// 
// 0 - End of Stream
// 1 - Component Add
// 2 - Component Remove
// 3 - Component Add checked
// 4 - Component Remove checked
// 5 - Full component data
// 6 - Role change
// 7 - Remote role change
// 8 - Python replication data
// 9 - Actor owner change
// 10 - Actor tag change

static_assert(Py_UNICODE_SIZE == 2, "Py_UNICODE type should be UCS2.");

bool ANetwork::Replicate(rn::BitStream* out, FRMConnection* dest, bool initial, bool nofields) {
    TPtr<CActor> actor = GetActor();
    ENetRole::Type role = actor->GetRole();
    ENetRole::Type remoteRole = actor->GetRemoteRole();
    assert(remoteRole != ENetRole::NONE);
    rn::BitSize_t start = out->GetWriteOffset();
    if (initial)
        mHasSentInitialFields = false;

    if (role == ENetRole::AUTHORITY) {
        // Write roles first
        if (role != mRecentRole) {
            uint16 index = PackIndex(CI_MSG, VI_ROLE_CHANGE);
            mRecentRole = role;

            out->Write(index);
            out->Write((uint8)role);
        }
        if (remoteRole != mRecentRemoteRole) {
            uint16 index = PackIndex(CI_MSG, VI_REMOTE_ROLE_CHANGE);
            mRecentRemoteRole = remoteRole;

            out->Write(index);
            out->Write((uint8)remoteRole);
        }

        // Write changes to components
        for (uint i = 0; i < mChanges.size(); i++) {
            if (mChanges[i].Added) {
                uint16 index = PackIndex(CI_MSG, VI_COMP_ADD);
                uint8 compIndex = mChanges[i].Index;
                assert(PyString_CheckExact(mChanges[i].ClassName.ptr()));
                assert(PyString_GET_SIZE(mChanges[i].ClassName.ptr()) <= MAX_GLOBAL_NAME_SIZE);

                out->Write(index);
                out->Write(compIndex);
                WriteStr(out, mChanges[i].ClassName, MAX_GLOBAL_NAME_SIZE);
            }
            else {
                uint16 index = PackIndex(CI_MSG, VI_COMP_REMOVE);
                uint8 compIndex = mChanges[i].Index;
                out->Write(index);
                out->Write(compIndex);
            }
        }
        mChanges.clear();

        // Write actor changes, only tag and owner are mutable
        if (!nofields) {
            TPtr<CActor> owner = actor->GetOwner();
            unicode const& tag = actor->GetTag();
            if (owner != mRecentOwner.Lock()) {
                uint16 index = PackIndex(CI_MSG, VI_OWNER_CHANGE);
                mRecentOwner = owner;
            
                out->Write(index);
                SerializePtr(out, owner);
            }
            // Strings are immutable, so do pointer compare
            if (tag.ptr() != mRecentTag.ptr()) {
                uint16 index = PackIndex(CI_MSG, VI_TAG_CHANGE);
                Py_UNICODE* buf = PyUnicode_AsUnicode(tag.ptr());
                Py_ssize_t size = PyUnicode_GET_SIZE(tag.ptr());
                if (size > MAX_TAG_SIZE)
                    size = MAX_TAG_SIZE;
                mRecentTag = tag;

                out->Write(index);
                out->Write((uint8)size);
                out->Write((char const*)buf, size * Py_UNICODE_SIZE);
            }
        }
    }
    else {
        assert(mChanges.size() == 0);
    }

    // Write changes to component fields
    for (uint i = 0; i < mData.size() && !nofields; i++) {
        FRepData& data = mData[i];
        TPtr<AComponent> comp = data.Component.Lock();
        assert(comp);

        data.Indices.assign(data.Indices.size(), -1);
        FFieldInfo const** fields = &data.Fields.front();
        byte const* recent = &data.Recent.front();
        sint16* indices = &data.Indices.front();

        // Initial implies dirty
        if (initial || !mHasSentInitialFields) {
            comp->mNetInitial = true;
            comp->mNetDirty = true;
        }
        
        // Determine net owner
        {
            //TPtr<APlayerController> topPC = GetTopPC(comp->GetActor());
            //TPtr<CPlayer> player = topPC ? topPC->GetPlayer() : TPtr<CPlayer>();
            //// Replicated player controllers do not have a player
            //FRMConnection* pcConn = player ? player->GetRMConnection() : nullptr;
            //bool isServer = dest->GetReplicaManager()->GetWorldPtr()->GetNetIsServer();
            //// We're the net owner if we're a server and the player that owns this component
            //// is the connection we're replicating to, or if we're a client and the player
            //// that owns this component is the local player.
            //comp->mNetOwner = isServer ? (pcConn == dest) : (player ? player->GetIsLocal() : false);
            comp->mNetOwner = CheckNetOwner(comp->GetActor(), dest);
        }
        
        // Variables specify themselves for replication by writing their index
        // to the indices array. Each variable writes a two byte index and a two byte
        // array offset iff the variable is an array type.
        comp->GetNativeRepVars(indices, recent, fields, this, actor.GetPtr());

        comp->mNetInitial = false;
        comp->mNetDirty = false;
        comp->mNetOwner = false;

        for (int e = 0; indices[e] != -1; e += 2) {
            FFieldInfo const* info = fields[indices[e]];
            uint16 offset = indices[e + 1];
            uint16 offbytes = 0;//offset * info->Size;

            // Copy current value to recent
            byte* valuep = ((byte*)comp.GetPtr()) + info->Offset + offbytes;
            if (info->Type == FT_WEAK_ACTOR || info->Type == FT_WEAK_COMPONENT) {
                // Special handling for weak refs
                PyObject* weakref = ((TWeakPtr<CObject>*)valuep)->GetHandle().get();
                assert(weakref == nullptr || PyWeakref_CheckRef(weakref));
                ((FRecentWeakPtr*)(recent + info->Offset))->Object =
                    weakref ? PyWeakref_GET_OBJECT(weakref) : nullptr;
            }
            else {
                memcpy((void*)(recent + info->Offset), valuep, info->Size);
            }

            // Write the index. The component's index in mData identifies it, which is
            // why changes are written first so that components are in their proper
            // indices before variables are written
            WriteIndex(out, i, info->Index);

            // Write the value
            switch (info->Type) {
            case FT_VECTOR3F:
                // Write a compressed vector
                {
                    FVector3F* vec = (FVector3F*)valuep;
                    out->WriteVector(vec->X, vec->Y, vec->Z);
                }
                break;
            case FT_PYOBJECT:
            case FT_PYSTR:
            case FT_PYUNICODE:
                break;
            case FT_ACTOR:
                break;
            case FT_WEAK_ACTOR:
                // Serialize actor reference using the network ID
                {
                    TPtr<CActor> actor = ((TWeakPtr<CActor>*)valuep)->Lock();
                    SerializePtr(out, actor);
                }
                break;
            case FT_COMPONENT:
                break;
            case FT_WEAK_COMPONENT:
                // Serialize component using the actor's network ID and the
                // network component's index for the replicated component
                {
                    TPtr<AComponent> comp = ((TWeakPtr<AComponent>*)valuep)->Lock();
                    SerializePtr(out, comp);
                }
                break;
            case FT_RESOURCE:
                break;
            default:
                // We default to writing the bytes directly
                out->Write((char const*)valuep, info->Size);
                break;
            }
        }
    }

    if (!nofields)
        mHasSentInitialFields = true;

    // Write end of stream if we've written anything so far
    if (out->GetWriteOffset() > start) {
        LOG_DEBUG(str("replicating: ") + str(GetActor()));
        WriteIndex(out, CI_MSG, VI_END);
        return true;
    }
    return false;
}

#define READ(expression) do { if (!(expression)) return false; } while (0)

bool ANetwork::Update(rn::BitStream* in, FRMConnection* src) {
    uint8 ci, role;
    uint16 vi;
    TPtr<CActor> actor = GetActor();

    while (ReadIndex(in, ci, vi)) {
        if (ci == CI_MSG) {
            if (vi == VI_END)
                break; // end of stream
            switch (vi) {
            case VI_COMP_ADD_CHECKED:
            case VI_COMP_ADD:
                {
                    uint8 compIndex;
                    object className;
                    READ(in->Read(compIndex));
                    READ(ReadStr(in, className, MAX_GLOBAL_NAME_SIZE));

                    try {
                        object cls = FindGlobal(className);
                        assert(compIndex == mData.size());
                        TPtr<AComponent> comp = extract<TPtr<AComponent>>(cls());
                        // Set a flag so we don't make this component addition as a change
                        // to be sent to the remote actor.
                        mUpdatingComps = true;
                        actor->AddComponent(comp);
                        mUpdatingComps = false;
                        assert(mData.at(compIndex).Component.Lock() == comp);
                    }
                    catch (...) {
                        mUpdatingComps = false;
                        actor->SetRemoteRole(ENetRole::NONE);
                        throw;
                    }
                }
                break;
            case VI_COMP_REMOVE_CHECKED:
            case VI_COMP_REMOVE:
                {
                    uint8 compIndex;
                    
                    READ(in->Read(compIndex));
                    assert(compIndex < CI_MSG);

                    if (compIndex >= mData.size())
                        break;

                    TPtr<AComponent> comp = mData[compIndex].Component.Lock();
                    assert(comp);

                    try {
                        mUpdatingComps = true;
                        actor->RemoveComponent(comp);
                        mUpdatingComps = false;
                    }
                    catch (...) {
                        mUpdatingComps = false;
                        throw;
                    }
                }
                break;
            // Role changes are reversed here
            case VI_ROLE_CHANGE:
                READ(in->Read(role));
                actor->SetRemoteRole((ENetRole::Type)role);
                break;
            case VI_REMOTE_ROLE_CHANGE:
                READ(in->Read(role));
                actor->SetRole((ENetRole::Type)role);
                break;
            case VI_COMP_FULL_DATA:
                break;
            case VI_PYTHON_REP:
                break;
            case VI_OWNER_CHANGE:
                {
                    TPtr<CActor> owner;
                    READ(DeserializePtr(in, this, owner));
                    actor->SetOwner(owner);

                    GetLoggerPtr()->Debug(str("Actor owner update ({0}): {1}").
                                          attr("format")(actor, owner));
                }
                break;
            case VI_TAG_CHANGE:
                {
                    uint8 size;
                    Py_UNICODE buf[0x100 * Py_UNICODE_SIZE];

                    READ(in->Read(size));
                    READ(in->Read((char*)buf, size * Py_UNICODE_SIZE));

                    size = Clamp(size, (uint8)0, MAX_TAG_SIZE);
                    unicode tag(detail::new_reference(PyUnicode_FromUnicode(buf, size)));
                    actor->SetTag(tag);
                    
                    GetLoggerPtr()->Debug(str("Actor tag update: ") + str(actor));
                }
                break;
            }
        }
        else {
            if (ci >= mData.size()) {
                // fail
                return false;
            }
            FRepData& data = mData[ci];
            if (vi >= data.Fields.size()) {
                // fail
                return false;
            }
            FFieldInfo const* info = data.Fields[vi];
            TPtr<AComponent> comp = data.Component.Lock();
            assert(comp);

            //LOG_DEBUG(str("Updating field: comp={0}, name={1}, type={2}").
            //              attr("format")(comp, info.Name, (int)info.Type));
            
            byte* valuep = ((byte*)comp.GetPtr()) + info->Offset;

            switch (info->Type) {
            case FT_VECTOR3F:
                {
                    auto vec = (FVector3F*)valuep;
                    READ(in->ReadVector(vec->X, vec->Y, vec->Z));
                    //LOG_DEBUG(str("  value: ") + str(*vec));
                }
                break;
            case FT_ACTOR:
                break;
            case FT_WEAK_ACTOR:
                {
                    auto tptr = (TWeakPtr<CActor>*)valuep;
                    TPtr<CActor> ractor;
                    READ(DeserializePtr(in, this, ractor));
                    *tptr = ractor;
                    LOG_DEBUG(str("  value: ") + str(ractor));
                }
                break;
            case FT_COMPONENT:
                break;
            case FT_WEAK_COMPONENT:
                {
                    auto tptr = (TWeakPtr<AComponent>*)valuep;
                    TPtr<AComponent> fcomp;
                    READ(DeserializePtr(in, this, fcomp));
                    *tptr = fcomp;
                    LOG_DEBUG(str("  value: ") + str(fcomp));
                }
                break;
            default:
                READ(in->Read((char*)valuep, info->Size));
                break;
            }

            if (info->Flags & FF_REPNOTIFY)
                comp->OnReplicated(info, &data.Fields.front());
        }
    }
    return true;
}

bool ANetwork::GetIsRelevantFor(
    FRMConnection* dest,
    FReplicaManager* rm)
{
    TPtr<CActor> actor = GetActor();
    assert(actor->GetRemoteRole() != ENetRole::NONE);

    TPtr<CActor> rviewer = dest->GetPlayer()->GetActor();

    if (actor->GetOnlyRelevantToOwner() && !actor->IsOwnedBy(rviewer))
        return false;

    if (actor->GetAlwaysRelevant() || actor->IsOwnedBy(rviewer))
        return true;

    TPtr<APlayerController> rviewerpc = rviewer->GetPlayerController();
    assert(rviewerpc);
    TPtr<CActor> viewer = rviewerpc->GetPawn() ? rviewerpc->GetPawn()->GetActor() : TPtr<CActor>();

    if (actor->IsOwnedBy(viewer) || actor == viewer)
        return true;

    FVector3F position = viewer ?
        (viewer->GetTransform() ? viewer->GetTransform()->GetPosition() : FVector3F()) :
        FVector3F();

    for (uint32 i = 0; i < actor->GetComponentCount(); i++) {
        TPtr<AComponent> comp = actor->GetComponent(i);
        if (comp.GetPtr() == this)
            continue;
        if (comp->GetNetTestRelevancy() && comp->GetNetIsRelevantFor(rviewer, viewer, position))
            return true;
    }

    return false;
}

void ANetwork::WriteAllocationID(rn::Connection_RM3* dest, rn::BitStream* out) const {
    // sint32 - levelNameHash
    // uint32 - actorID
    // rn::NetworkID - ownerNetID
    TPtr<CActor> actor = GetActor();
    if (!actor)
        throw RuntimeError("no owner to write allocation ID");
    TPtr<CActor> owner = actor->GetOwner();
    TPtr<CLevel> level = GetLevel();
    assert(level);
    //object guidBytes = level->GetGUID().attr("bytes");
    //assert(len(guidBytes) == 16);

    // NOTE: Only the base actor is allocated using this method. Component creation is handled
    //       by the network component.
    out->Write(level->GetNameHashAsInt());
    //WriteStr(out, guidBytes, 16);
    out->Write(actor->GetID());
    if (owner && owner->GetNetwork() && owner->GetRemoteRole() != ENetRole::NONE) {
        assert(owner->GetNetwork()->GetNetworkID() != rn::UNASSIGNED_NETWORK_ID);
        out->Write(owner->GetNetwork()->GetNetworkID());
    }
    else {
        out->Write(rn::UNASSIGNED_NETWORK_ID);
    }
}

namespace {

template <typename Return>
Return call_raw(object const& callable, object const& args=make_tuple(), object const& kwds=object()) {
    return call_raw<Return>(callable.ptr(), args, kwds);
}

template <typename Return>
Return call_raw(PyObject* callable, object const& args=make_tuple(), object const& kwds=object()) {
    return extract<Return>(object(handle<>(PyObject_Call(callable, args.ptr(), kwds.ptr()))));
}

template <>
object call_raw<object>(PyObject* callable, object const& args, object const& kwds) {
    return object(handle<>(PyObject_Call(callable, args.ptr(), kwds.ptr())));
}

template <>
void call_raw<void>(PyObject* callable, object const& args, object const& kwds) {
    handle<>(PyObject_Call(callable, args.ptr(), kwds.ptr()));
}

}

// Alloc method placed here for convenience
rn::Replica3* FRMConnection::AllocReplica(rn::BitStream* input, rn::ReplicaManager3* rmx) {
    FReplicaManager* rm = static_cast<FReplicaManager*>(rmx);
    sint32 levelNameHash;
    //object guidBytes;
    uint32 actorID;
    rn::NetworkID ownerID;

    input->Read(levelNameHash);
    //ReadStr(input, guidBytes, 16);
    input->Read(actorID);
    input->Read(ownerID);

    object levelNameHashObj(handle<>(PyInt_FromLong(levelNameHash)));
    //object guid;
    //{
    //    dict kwds;
    //    kwds["bytes"] = guidBytes;
    //    guid = call_raw<object>(CWorld::GetGUIDClass(), make_tuple(), kwds);
    //}
    TPtr<CLevel> level = rm->GetWorld()->GetLevel(levelNameHashObj);
    //TPtr<CLevel> level = rm->GetWorld()->GetLevel(guid);
    if (!level)
        //throw RuntimeError("matching level not found when attempting to allocate replica");
        return nullptr;

    TPtr<CActor> owner;
    if (ownerID != rn::UNASSIGNED_NETWORK_ID) {
        owner = rm->GetWorld()->NetGetActor(ownerID);
        if (!owner)
            return nullptr;
            //throw RuntimeError("matching owner not found when attempting to allocate replica");
    }
    
    // TODO: This direct class access should only be for testing
    object netcompcls = FindGlobal("kharlia.engine.actor.ANetwork");

    TPtr<CActor> actor = level->CreateActor(netcompcls, owner);
    TPtr<ANetwork> net = actor->GetNetwork();
    if (!net)
        return nullptr;

    GetLoggerPtr()->Debug(
        str("Allocated replica: levelname={0}, ID={1}, owner={2}").
            attr("format")(level->GetName(), actor->GetID(), owner));


    return net.GetPtr();
}

rn::RM3ConstructionState ANetwork::QueryConstruction(rn::Connection_RM3* destb, rn::ReplicaManager3* rmb) {
    auto dest = static_cast<FRMConnection*>(destb);
    auto rm = static_cast<FReplicaManager*>(rmb);

    if (rm->GetPeer()->GetIsServer() &&
            GetActor()->GetRemoteRole() != ENetRole::NONE && GetIsRelevantFor(dest, rm))
        return rn::RM3CS_SEND_CONSTRUCTION;

    return rn::RM3CS_NO_ACTION;
    //TPtr<CActor> actor = GetActor();

    //if (mOnlyRelevantToOwner && dest->GetPlayer()->GetController() != GetTopOwner(actor))
    //    return rn::RM3CS_NO_ACTION;

    //TPtr<APlayerController> rviewer = dest->GetPlayer()->GetController();
    //TPtr<CActor> viewer = rviewer->GetPawn() ? rviewer->GetPawn()->GetActor() : TPtr<CActor>();
    //FVector3F position = viewer ?
    //    (viewer->GetTransform() ? viewer->GetTransform()->GetPosition() : FVector3F()) :
    //    FVector3F();

    //if (GetIsRelevantFor(rviewer, viewer, position))
    //    return rn::RM3CS_SEND_CONSTRUCTION;

    //if (mRemoteRole != ROLE_NONE) {
    //    return QueryConstruction_ServerConstruction(
    //               dest, GetLevel()->GetWorld()->GetNetIsServer());
    //}
    //else {
    //    return rn::RM3CS_NO_ACTION;
    //}
}

rn::RM3DestructionState ANetwork::QueryDestruction(rn::Connection_RM3 *destb, rn::ReplicaManager3 *rmb) {
    // Destruction is handled expicitly by setting RemoteRole to ENetRole::NONE
    //return rn::RM3DS_NO_ACTION;
    auto dest = static_cast<FRMConnection*>(destb);
    auto rm = static_cast<FReplicaManager*>(rmb);
    if (rm->GetPeer()->GetIsServer() &&
            (GetActor()->GetRemoteRole() == ENetRole::NONE || !GetIsRelevantFor(dest, rm)))
        return rn::RM3DS_SEND_DESTRUCTION;

    return rn::RM3DS_NO_ACTION;
}

bool ANetwork::QueryRemoteConstruction(rn::Connection_RM3* source) {
    // The server constructs actors on the client, but not vice versa.
    auto conn = static_cast<FRMConnection*>(source);
    return QueryRemoteConstruction_ServerConstruction(
        source, conn->GetReplicaManager()->GetPeer()->GetIsServer());
}

void ANetwork::SerializeConstruction(rn::BitStream* bs, rn::Connection_RM3* dest) {
    mHasSentInitialFields = false;
    Replicate(bs, static_cast<FRMConnection*>(dest), true, true);
}

bool ANetwork::DeserializeConstruction(rn::BitStream* bs, rn::Connection_RM3* source) {
    mHasSentInitialFields = false;
    Update(bs, static_cast<FRMConnection*>(source));
    return true;
}

void ANetwork::SerializeDestruction(rn::BitStream* bs, rn::Connection_RM3* dest) {
    Replicate(bs, static_cast<FRMConnection*>(dest), false);
}

bool ANetwork::DeserializeDestruction(rn::BitStream* bs, rn::Connection_RM3* source) {
    Update(bs, static_cast<FRMConnection*>(source));
    return true;
}

rn::RM3ActionOnPopConnection ANetwork::QueryActionOnPopConnection(rn::Connection_RM3 *connb) const {
    auto conn = static_cast<FRMConnection*>(connb);
    FNetPeer* peer = conn->GetReplicaManager()->GetPeer();
  
    if (peer->GetIsServer()) {
        TPtr<APlayerController> pc = conn->GetPlayer()->GetActor()->GetPlayerController();
        assert(GetActor()->IsOwnedBy(pc->GetActor()));
        return rn::RM3AOPC_DELETE_REPLICA_AND_BROADCAST_DESTRUCTION;
    }
    else {
        assert(peer->GetIsClient());
        return rn::RM3AOPC_DELETE_REPLICA;
    }
}

rn::RM3QuerySerializationResult ANetwork::QuerySerialization(rn::Connection_RM3 *dest) {
    // Both clients replicate to server
    // and server replicates to clients
    if (GetActor()->GetRemoteRole() == ENetRole::NONE) {
        return rn::RM3QSR_DO_NOT_CALL_SERIALIZE;
    }
    else {
        return rn::RM3QSR_CALL_SERIALIZE;
    }
}

rn::RM3SerializationResult ANetwork::Serialize(rn::SerializeParameters* sp) {
    //assert(sp->pro[0].reliability == RELIABLE_SEQUENCED);
    sp->pro[0].reliability = RELIABLE_SEQUENCED;
    auto conn = static_cast<FRMConnection*>(sp->destinationConnection);
    if (Replicate(sp->outputBitstream + 0, conn, false)) {
        return rn::RM3SR_SERIALIZED_ALWAYS;
    }
    else {
        return rn::RM3SR_DO_NOT_SERIALIZE;
    }
}

void ANetwork::Deserialize(rn::DeserializeParameters* dp) {
    Update(dp->serializationBitstream + 0, static_cast<FRMConnection*>(dp->sourceConnection));
}

void ANetwork::DeallocReplica(rn::Connection_RM3* source) {
    GetLevel()->DestroyActor(GetActor());
}

void ANetwork::BroadcastDestruction() {
    if (this->replicaManager)
        rn::Replica3::BroadcastDestruction();
}

namespace {

FFieldInfo const* GetFieldInfoRep(FFieldInfo const** fields, char const* name) {
    FFieldInfo const* info = GetFieldInfo(fields, name);
    if (!info)
        throw RuntimeError(format("GetNativeRepVars(): Undefined field %1%") % name);
    return info;
}

template <typename T, typename U>
inline bool CompareNeq(T const* recent, U const* current) {
    return *recent != *current;
}

template <typename T>
inline bool CompareNeq(TWeakPtr<T> const* recent, TWeakPtr<T> const* current) {
    return ((FRecentWeakPtr*)recent)->Object != current->Lock().GetHandle().get();
}

#define DEF_REP(typename_) \
    sint16* typename_::GetNativeRepVars(sint16* indices, byte const* recent, \
                                        FFieldInfo const** fields, ANetwork const* net, \
                                        CActor const* act)

#define REP_BASE(typename_) indices = typename_::GetNativeRepVars(indices, recent, fields, net, act)

#define REP_RETURN return indices

#define REP(typename_, varname) \
    do {\
        if (CompareNeq(&reinterpret_cast<typename_ const*>(recent)->varname, &this->varname)) { \
            static FFieldInfo const* info = GetFieldInfoRep(fields, #varname); \
            indices[0] = info->Index; \
            indices[1] = 0; \
            indices+=2; \
        } \
    } while(0)

#define REPA(typename_, varname) \
    do {\
        static FFieldInfo const* info = GetFieldInfoRep(fields, #varname); \
        static uint16 const fas = info->ArraySize; \
        for (int i = 0; i < fas; i++) { \
            if (CompareNeq(&reinterpret_cast<typename_ const*>(recent)->varname[i], &this->varname[i])) { \
                indices[0] = info->Index; \
                indices[1] = i; \
                indices+=2; \
            } \
        } \
    } while(0)

} // namespace

// Test implementations for replication
DEF_REP(AComponent) {
    REP_RETURN;
}

DEF_REP(ATransform) {
    REP_BASE(AComponent);

    if (mNetDirty && ((act->GetRole() == ENetRole::AUTHORITY && mNetInitial) ||
                      (act->GetRole() == ENetRole::AUTO_PROXY && !mNetInitial))) {     
        REP(ATransform, mPosition);
    }

    REP_RETURN;
}

DEF_REP(AMovement) {
    REP_BASE(AComponent);
    
    if (mNetDirty && ((act->GetRole() == ENetRole::AUTHORITY && mNetInitial) ||
                      (act->GetRole() == ENetRole::AUTO_PROXY && !mNetInitial))) {     
        REP(AMovement, mVelocity);
        REP(AMovement, mAcceleration);
        REP(AMovement, mMaxVelocity);
        REP(AMovement, mMaxAcceleration);
    }
    
    REP_RETURN;
}

DEF_REP(ACamera) {
    REP_BASE(AComponent);

    if (mNetDirty && act->GetRole() == ENetRole::AUTHORITY) {
        REP(ACamera, mTarget);
        REP(ACamera, mTargetOffset);
    }

    REP_RETURN;
}

DEF_REP(AController) {
    REP_BASE(AComponent);

    if (mNetDirty && act->GetRole() == ENetRole::AUTHORITY) {
        REP(AController, mPawn);
    }
    
    REP_RETURN;
}

DEF_REP(APlayerController) {
    REP_BASE(AController);

    if (mNetDirty && act->GetRole() == ENetRole::AUTHORITY) {
        REP(APlayerController, mCamera);
    }
    
    REP_RETURN;
}

DEF_REP(APawn) {
    REP_BASE(AComponent);

    if (mNetDirty && act->GetRole() == ENetRole::AUTHORITY) {
        REP(APawn, mController);
    }
    
    REP_RETURN;
}

}} // namespace kharlia::core